package com.lhz.project.system.service.base.impl;

import cn.hutool.core.lang.Assert;
import com.lhz.common.constant.*;
import com.lhz.common.utils.MD5Utils;
import com.lhz.common.utils.RedisUtils;
import com.lhz.common.utils.UUIDUtils;
import com.lhz.common.utils.ip.IpUtils;
import com.lhz.framework.config.ThreadPoolConfig;
import com.lhz.framework.exception.ServiceException;
import com.lhz.framework.manager.threadManager.LoginLogThreadManager;
import com.lhz.framework.manager.threadManager.factory.AsyncBean;
import com.lhz.framework.security.HolderInfoManager;
import com.lhz.framework.security.HolderUserInfo;
import com.lhz.framework.security.manager.TokenManager;
import com.lhz.framework.security.token.DataTokenInfo;
import com.lhz.framework.security.token.DeptTokenInfo;
import com.lhz.framework.security.token.MenuTokenInfo;
import com.lhz.framework.security.token.UserTokenInfo;
import com.lhz.project.system.mapper.auth.*;
import com.lhz.project.system.mapper.dept.DeptMapper;
import com.lhz.project.system.model.entity.auth.*;
import com.lhz.project.system.model.entity.dept.Dept;
import com.lhz.project.system.model.param.base.LoginParam;
import com.lhz.project.system.model.vo.auth.MetaVo;
import com.lhz.project.system.model.vo.base.LoginVo;
import com.lhz.project.system.service.auth.LoginService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by LiGezZ
 * Date on 2021/5/4 13:38
 */
@Service
public class LoginServiceImpl implements LoginService {

    @Resource
    private LoginMapper loginMapper;
    @Resource
    private UserMapper userMapper;

    @Resource
    private MenuMapper menuMapper;

    @Resource
    private DeptMapper deptMapper;

    @Resource
    private UserRoleMapper userRoleMapper;

    @Resource
    private RoleDeptMapper roleDeptMapper;

    @Resource
    private TokenManager tokenManager;

    @Resource
    private HttpServletRequest request;

    @Resource
    private ThreadPoolConfig threadPoolConfig;

    @Override
    public void logout() {
        String userIdKey = TokenConstant.TOKEN_REDIS_KEY + HolderInfoManager.getUserId();
        // token key：以登录应用区分，同一应用限制多登录，不同应用可以同时登录
        if (RedisUtils.hasKey(userIdKey)) {
            String oldToken = RedisUtils.get(userIdKey).toString();
            RedisUtils.del(userIdKey, oldToken);
        }
        setLoginLog(HolderInfoManager.getUserName(), SysConstants.SUCCESS, "注销成功");
    }

    @Override
    public LoginVo login(LoginParam param) {
        String userName = param.getUsername();
        //判断账户是否存在
        Account userAccount = loginMapper.getAccount(param.getUsername());
        if (userAccount == null) {
            setLoginLog(userName, SysConstants.FAIL, ResponseState.ACCOUNT_UNDEFINED.getName());
            throw new ServiceException(ResponseState.ACCOUNT_UNDEFINED);
        }
        //密码解码
        boolean md5 = MD5Utils.getSaltVerifyMd5(param.getPassword(), userAccount.getPassword());
        if (!md5) {
            setLoginLog(userName, SysConstants.FAIL, ResponseState.PASS_UNDEFINED.getName());
            throw new ServiceException(ResponseState.PASS_UNDEFINED);
        }

        return this.createToken(userAccount);
    }

    //成功登陆以后设置Token操作,并且封装后返回给前端
    private LoginVo createToken(Account account) {
        String userId = account.getUserId();
        //获取token值
        String token = UUIDUtils.getUuId();
        String userName = account.getUsername();

        //查询用户信息
        User user = userMapper.selectByPrimaryKey(userId);
        if (user == null) {
            setLoginLog(userName, SysConstants.FAIL, ResponseState.ACCOUNT_UNDEFINED.getName());
            throw new ServiceException(ResponseState.ACCOUNT_UNDEFINED);
        }
        //通过账户userId查询用户是否有效
        if (user.getStatus() == DataStatusEnum.INVALID.getCode()) {
            setLoginLog(userName, SysConstants.FAIL, ResponseState.ACCOUNT_UNDEFINED.getName());
            throw new ServiceException(ResponseState.ACCOUNT_UNDEFINED);
        }

        //获取用户类型
        String userType = user.getType();
        DeptTokenInfo deptInfo = new DeptTokenInfo();
        //查询部门信息
        Dept dept = deptMapper.selectByPrimaryKey(user.getDeptId());
        BeanUtils.copyProperties(dept, deptInfo);

        //查询角色信息
        List<String> roleList = new ArrayList<>();
        //管理员默认角色
        List<Role> roles = userRoleMapper.selectRolesByUserId(userId);
        if (userType.equals(AccountTypeConstant.ADMIN)) {
            roleList.add(AccountTypeConstant.ADMIN);
        } else {
            Assert.notEmpty(roles, ResponseState.NOT_ROLE.name());
            roleList.addAll(roles.stream().map(Role::getCode).toList());
        }
        //查询权限范围
        DataTokenInfo dataTokenInfo = getUserDataScope(userType, roles);

        //查询菜单，管理员userId传null
        String newUserId = null;
        if (!userType.equals(AccountTypeConstant.ADMIN)) {
            newUserId = userId;
        }
        List<Menu> menuList = menuMapper.treeMenuListByUserId(newUserId);

        //获取目录/菜单列表转换为树形结构
        List<MenuTokenInfo> menus = getMenuTree(menuList);

        //获取权限编码
        List<String> permissionList = getMenuPermission(userType, menuList);

        //用户信息
        UserTokenInfo userTokenInfo = new UserTokenInfo();
        BeanUtils.copyProperties(user, userTokenInfo);

        //封装用户token基本信息
        HolderUserInfo userInfo = new HolderUserInfo();
        userInfo.setDept(deptInfo);
        userInfo.setUser(userTokenInfo);
        userInfo.setMenu(menus);
        userInfo.setRoleList(roleList);
        userInfo.setDataTokenInfo(dataTokenInfo);
        userInfo.setPermissionList(permissionList);

        tokenManager.saveUserToken(token, userName, userId, userInfo);

        LoginVo loginVo = new LoginVo();
        loginVo.setTokenName(TokenConstant.TOKEN_NAME);
        loginVo.setToken(token);
        loginVo.setUserName(userName);
        loginVo.setUserInfo(user);
        loginVo.setMenu(menus);
        loginVo.setRoleList(roleList);
        loginVo.setPermissionList(permissionList);

        //更新登录记录
        loginMapper.updateAccountLogin(account.getId(), IpUtils.getIpAddr(request), new Date());

        //记录日志
        setLoginLog(userName, SysConstants.SUCCESS, ResponseState.SUCCESS.getName());
        return loginVo;
    }

    private List<MenuTokenInfo> getMenuTree(List<Menu> menus) {
        return buildMenuTree(menus);
    }

    private List<String> getMenuPermission(String userType, List<Menu> menus) {
        List<String> perms = new ArrayList<>();
        // 管理员拥有所有权限
        if (userType.equals(AccountTypeConstant.ADMIN)) {
            perms.add("*:*:*");
        } else {
            //一个菜单可能存在多个权限，先将所有结果用逗号合并，在进行拆开
            String per = menus.stream().map(Menu::getPerms).collect(Collectors.joining(","));
            //处理一个菜单多个权限去情况
            perms.addAll(Arrays.asList(per.split(",")));
        }
        return perms;
    }


    private DataTokenInfo getUserDataScope(String userType, List<Role> roleList) {
        DataTokenInfo dataTokenInfo = new DataTokenInfo();
        Integer scope;
        List<Integer> deptList = new ArrayList<>();
        // 管理员拥有所有权限
        if (userType.equals(AccountTypeConstant.ADMIN)) {
            scope = DataScopeConstant.ALL;
        } else {
            //判断多个角色返回为，取最高进行实现(默认第一个权限最高)
            scope = roleList.get(0).getDataScope();

            //如果为自定义类型，那个根据角色id集合查询部门id集合
            if (scope.equals(DataScopeConstant.DIY)) {
                List<Integer> roleIds = roleList.stream().map(Role::getId).collect(Collectors.toList());
                deptList.addAll(roleDeptMapper.selectDeptIdByRoleIds(roleIds));
            }
        }
        dataTokenInfo.setDataScope(scope);
        dataTokenInfo.setDeptIds(deptList);
        return dataTokenInfo;
    }

    private void setLoginLog(String userName, Integer status, String message) {
        String id = UUIDUtils.getUuId();
        LoginInfo loginInfo = new LoginInfo();
        loginInfo.setLogId(id);
        loginInfo.setUsername(userName);
        loginInfo.setStatus(status);
        loginInfo.setMessage(message);
        AsyncBean asyncBean = new AsyncBean(LoginLogThreadManager.class, id, loginInfo);

        threadPoolConfig.start(asyncBean);
    }

    /**
     * 构建前端所需要树结构
     *
     * @return 树结构列表
     */
    private List<MenuTokenInfo> buildMenuTree(List<Menu> menus) {
        //新集合
        List<MenuTokenInfo> returnList = new ArrayList<>();

        List<MenuTokenInfo> menuList = new ArrayList<>();
        //获取id，并且实现转换
        List<Integer> tempList = new ArrayList<>();
        for (Menu menu : menus) {
            //获取id
            tempList.add(menu.getId());

            //转换实体
            MenuTokenInfo treeVo = new MenuTokenInfo();
            BeanUtils.copyProperties(menu, treeVo);
            //如果为目录设置默认组件
            String component = menu.getMenuType().equals(DataStatusConstants.TYPE_DIR) ? SysConstants.LAYOUT : menu.getComponent();
            treeVo.setComponent(component);
            MetaVo metaVo = new MetaVo();
            metaVo.setIcon(menu.getIcon());
            metaVo.setTitle(menu.getName());
            treeVo.setMeta(metaVo);
            menuList.add(treeVo);
        }

        //设置层级结构
        for (MenuTokenInfo menu : menuList) {
            // 如果是顶级节点, 遍历该父节点的所有子节点
            if (!tempList.contains(menu.getParentId())) {
                recursionFn(menuList, menu);
                returnList.add(menu);
            }
        }
        //没有查询到节点则以当前节点
        if (returnList.isEmpty()) {
            returnList = menuList;
        }
        return returnList;
    }

    /**
     * 递归列表
     */
    private void recursionFn(List<MenuTokenInfo> list, MenuTokenInfo t) {
        // 得到子节点列表
        List<MenuTokenInfo> childList = getChildList(list, t);
        t.setChildren(childList);
        for (MenuTokenInfo tChild : childList) {
            // 判断是否有子节点
            if (Objects.isNull(tChild.getParentId()) && tChild.getParentId().equals(t.getId())) {
                for (MenuTokenInfo n : childList) {
                    recursionFn(list, n);
                }
            }
        }
    }

    /**
     * 得到子节点列表
     */
    private List<MenuTokenInfo> getChildList(List<MenuTokenInfo> list, MenuTokenInfo t) {
        List<MenuTokenInfo> tList = new ArrayList<>();
        for (MenuTokenInfo n : list) {
            if (Objects.nonNull(n.getParentId()) && n.getParentId().equals(t.getId())) {
                tList.add(n);
            }
        }
        return tList;
    }
}
